package eclant.ant.tasks;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.ProjectComponent;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaModelException;

import eclant.ant.types.FileSetClasspathVisitor.EntryKind;
import eclant.eclipse.jdt.ClasspathVisitor;

class ClasspathEntriesLogger implements ClasspathVisitor
{
	private ContextNode context = null;
	private ContextNode root = null;
	private final Set<String> visited = new HashSet<String>();
	private final boolean failOnError;
	private final ProjectComponent comp;

	ClasspathEntriesLogger(ProjectComponent logTo, boolean failOnError)
	{
		this.failOnError = failOnError;
		this.comp = logTo;
	}

	public void printTree()
	{
		comp.log(root.path);
		if (root.entries != null)
			logTo(comp, root.entries.iterator(), "");
	}

	private void logTo(ProjectComponent comp, Iterator<ContextNode> iterator, String indent)
	{
		while (iterator.hasNext())
		{
			ContextNode node = iterator.next();
			String s = (iterator.hasNext() ? "|-- " : "`-- ");
			comp.log(indent + s + node.path);
			if (node.entries != null)
			{
				s = (iterator.hasNext() ? "|   " : "    ");
				logTo(comp, node.entries.iterator(), indent + s);
			}
		}
	}

	@Override
	public void externalLibrary(IPath name, boolean duplicate)
	{
		addLeaf(name.toOSString(), EntryKind.lib.toString(), duplicate);
	}

	@Override
	public void library(IFolder classFolder, boolean duplicate)
	{
		addLeaf(classFolder.getFullPath().toPortableString(), EntryKind.lib.toString(), duplicate);
	}

	@Override
	public void library(IFile jarFile, boolean duplicate)
	{
		addLeaf(jarFile.getFullPath().toPortableString(), EntryKind.lib.toString(), duplicate);
	}

	@Override
	public boolean enter(IClasspathContainer container)
	{
		String kind;
		switch (container.getKind())
		{
			case IClasspathContainer.K_APPLICATION:
				kind = "application";
				break;
			case IClasspathContainer.K_SYSTEM:
				kind = "system";
				break;
			case IClasspathContainer.K_DEFAULT_SYSTEM:
				kind = "default-system";
				break;
			default:
				kind = "?(code=" + String.valueOf(container.getKind()) + ")";
				break;
		}
		enter(container.getPath(), EntryKind.con.toString() + ":" + kind);
		return true;
	}

	@Override
	public boolean enter(IJavaProject project)
	{
		enter(project.getPath(), EntryKind.project.toString());
		try
		{
			addLeaf(project.getOutputLocation().toPortableString(), EntryKind.output.toString(),
					false);
		}
		catch (JavaModelException e)
		{
			if (failOnError)
				throw new BuildException(project.getPath().toPortableString(), e, comp
						.getLocation());
			else
				addLeaf(e.toString(), "error", false);
		}
		return true;
	}

	@Override
	public void exit()
	{
		context = context.parent;
	}

	@Override
	public void previouslyEncountered(IJavaProject project)
	{
		addLeaf(project.getProject().getFullPath().toPortableString(),
				EntryKind.project.toString(), true);
	}

	@Override
	public void source(IJavaProject project, IFolder srcFolder, IFolder outputFolder,
			boolean duplicate)
	{
		if (duplicate || outputFolder == null)
		{
			addLeaf(srcFolder.getFullPath().toPortableString(), EntryKind.src.toString(), duplicate);
		}
		else
		{
			enter(srcFolder.getFullPath(), EntryKind.src.toString());
			addLeaf(outputFolder.getFullPath().toPortableString(), //
					EntryKind.output.toString(), !visited.add(outputFolder.getFullPath()
							.toPortableString()));
			exit();
		}
	}

	@Override
	public void resolveFailed(IClasspathEntry entry)
	{
		addLeaf("[?" + entry.getPath() + "?]", "variable", false);
	}

	@Override
	public void resolved(IClasspathEntry entry, IClasspathEntry resolved)
	{
		addLeaf("[" + entry.getPath() + "]", "variable", false);
	}

	private void enter(IPath path, String kindInfo)
	{
		context = new ContextNode("[" + kindInfo + "] " + path.toPortableString(), context, true);
		if (root == null)
			root = context;
	}

	private void addLeaf(String path, String kindInfo, boolean duplicate)
	{
		path = "[" + kindInfo + "] " + path;
		if (duplicate)
			path = "( " + path + " )";
		new ContextNode(path, context, false);
	}

	private static final class ContextNode
	{
		final String path;
		final List<ContextNode> entries;
		final ContextNode parent;

		ContextNode(String path, ContextNode parent, boolean node)
		{
			this.path = path;
			this.entries = (node ? new ArrayList<ContextNode>() : null);
			this.parent = parent;
			if (parent != null)
				parent.entries.add(this);
		}
	}

}
